/*
 * Copyright (C) 2013  mistrnet.cz
 *
 * This file is part of p2pbackup.
 *
 * p2pbackup is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * p2pbackup is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with p2pbackup.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif//_GNU_SOURCE

#ifndef _REENTRANT
#define _REENTRANT
#endif//_REENTRANT

#include "config.h"
#include <cstdlib>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <vector>
#include <string>
#include <string.h>
#include <sys/time.h>
#include <iomanip>
#include <netdb.h>
#include <stdexcept>
#include <arpa/inet.h>
#include <signal.h>
#include <memory>

void dns_test();

namespace
{
    struct ::timeval t0 = {0, 0};
    void handle_resolve(const boost::system::error_code& ec, boost::asio::ip::tcp::resolver::iterator ep_it)
    {
        struct ::timeval now;
        struct ::timeval dT;
        ::gettimeofday(&now, NULL);
        timersub(&now, &t0, &dT);
        std::cout << dT.tv_sec << "." << std::setw(6) << std::setfill('0') << dT.tv_usec << " - ";
        t0 = now;
        if (!ec) {        // print ip addresses
            std::cout << ep_it->host_name() << std::endl;
        }
        else {
            std::cout << "Resolve error: " << ec << std::endl;
        }
//        std::ostringstream out;
//        out << dT.tv_sec << "." << std::setw(6) << std::setfill('0') << dT.tv_usec << std::setfill(' ') << " - ";
//        if (!ec) {        // print ip addresses
//            for (boost::asio::ip::tcp::resolver::iterator end; ep_it != end; ++ep_it) {
//                out << ep_it->host_name() << ": " << ep_it->endpoint().address().to_string() << std::endl;
//            }
//            std::cout << out.str();
//        }
//        else {
//            out << "Resolve error: " << ec << std::endl;
//            std::cerr << out.str();
//        }
    }
}

typedef std::vector< std::string > VecOfString;
typedef std::vector< boost::asio::ip::tcp::resolver::query* > VecOfPQuery;

void test(const VecOfString &_host)
{
    boost::asio::io_service io_service;
    boost::asio::ip::tcp::resolver resolver(io_service);
    VecOfPQuery vec_of_query;
    for (VecOfString::const_iterator pHost = _host.begin(); pHost != _host.end(); ++pHost) {
//        std::cout << "new boost::asio::ip::tcp::resolver::query(\"" << (*pHost) << "\", \"\")" << std::endl;
        vec_of_query.push_back(new boost::asio::ip::tcp::resolver::query(*pHost, ""));
    }
    ::gettimeofday(&t0, NULL);
    for (VecOfPQuery::const_iterator pPQuery = vec_of_query.begin(); pPQuery != vec_of_query.end(); ++pPQuery) {
//        if (pPQuery != vec_of_query.begin()) {
//            ::usleep(10 * 1000);
//        }
        resolver.async_resolve(**pPQuery, boost::bind(&handle_resolve, _1, _2));
        ::usleep(10 * 1000);
    }
//    ::usleep(100 * 1000);
//    ::sleep(1);
//    std::cout << "io_service.run() call" << std::endl;
//    ::gettimeofday(&t0, NULL);
    io_service.run();
//    std::cout << "io_service.run() done" << std::endl;
    for (VecOfPQuery::const_iterator pPQuery = vec_of_query.begin(); pPQuery != vec_of_query.end(); ++pPQuery) {
        delete *pPQuery;
    }
}

std::ostream& operator<<(std::ostream &out, const struct ::addrinfo &ai);
std::ostream& operator<<(std::ostream &out, const struct ::in_addr &a);
std::ostream& operator<<(std::ostream &out, const struct ::in6_addr &a);

//void getaddrinfo_sighandler(int sig_num, ::siginfo_t *sig_info, void*);

::size_t number_of_threads();

void gnu_test(const VecOfString &_host)
{
    dns_test();
    return;
    std::auto_ptr< struct ::addrinfo > pRequest(new struct ::addrinfo);
    std::auto_ptr< struct ::addrinfo > result(new struct ::addrinfo[_host.size()]);
    std::auto_ptr< struct ::gaicb > query(new struct ::gaicb[_host.size()]);
    std::auto_ptr< struct ::gaicb* > pQuery(new struct ::gaicb*[_host.size()]);
    const struct ::gaicb **const pPQueryEnd = const_cast< const struct ::gaicb** >(pQuery.get() + _host.size());
//    volatile bool query_done = false;

    std::memset(pRequest.get(), 0, sizeof(struct ::addrinfo));
    std::memset(result.get(), 0, sizeof(struct ::addrinfo[_host.size()]));
    std::memset(query.get(), 0, sizeof(struct ::gaicb[_host.size()]));
    std::memset(pQuery.get(), 0, sizeof(struct ::gaicb*[_host.size()]));
    pRequest->ai_flags = AI_ADDRCONFIG;
    pRequest->ai_family = AF_UNSPEC;
    pRequest->ai_socktype = SOCK_STREAM;
    pRequest->ai_protocol = IPPROTO_TCP;
    int idx = 0;
    for (VecOfString::const_iterator pHost = _host.begin(); pHost != _host.end(); ++pHost, ++idx) {
        query.get()[idx].ar_name = pHost->c_str();
        query.get()[idx].ar_service = "http";
        query.get()[idx].ar_request = pRequest.get();
        query.get()[idx].ar_result = result.get() + idx;
        pQuery.get()[idx] = query.get() + idx;
    }
//    struct ::sigevent my_sig_handler;
//    my_sig_handler.sigev_notify = SIGEV_SIGNAL;
//    my_sig_handler.sigev_signo = SIGUSR1;
//    my_sig_handler.sigev_value.sival_ptr = const_cast< bool* >(&query_done);
//    struct ::sigaction my_sigaction;
//    struct ::sigaction old_sigaction;
//    my_sigaction.sa_sigaction = getaddrinfo_sighandler;
//    my_sigaction.sa_flags = SA_SIGINFO;
//    ::sigemptyset(&my_sigaction.sa_mask);
//    ::sigaction(my_sig_handler.sigev_signo, &my_sigaction, &old_sigaction);
    struct ::timeval t0;
    ::gettimeofday(&t0, NULL);
    const int c_error = getaddrinfo_a(GAI_NOWAIT, pQuery.get(), _host.size(), NULL/*&my_sig_handler*/);
    enum { SUCCESS = 0 };
    switch (c_error) {
    case SUCCESS:
        break;
    case EAI_AGAIN:
        throw std::runtime_error(std::string("EAI_AGAIN ") + ::gai_strerror(c_error));
    case EAI_MEMORY:
        throw std::runtime_error(std::string("EAI_MEMORY ") + ::gai_strerror(c_error));
    case EAI_SYSTEM:
        throw std::runtime_error(std::string("EAI_SYSTEM ") + ::gai_strerror(c_error));
    default:
        std::ostringstream o;
        o << "EAI_" << c_error << " " << ::gai_strerror(c_error);
        throw std::runtime_error(o.str());
    }
    std::cout << number_of_threads() << " threads" << std::endl;

//    while (!query_done) {
//        ::pause();
//    }

    while (::gai_suspend(pQuery.get(), _host.size(), NULL) != EAI_ALLDONE) {
    }

    struct ::timeval t1;
    ::gettimeofday(&t1, NULL);
    struct ::timeval dT;
    timersub(&t1, &t0, &dT);
    std::cout << number_of_threads() << " threads" << std::endl;
    std::cout << "resolved in " << dT.tv_sec << "." << std::setw(6) << std::setfill('0') << dT.tv_usec << std::endl;
    for (struct ::gaicb **pPQuery = pQuery.get(); pPQuery < pPQueryEnd; ++pPQuery) {
        const int c_error = ::gai_error(const_cast< struct ::gaicb* >(*pPQuery));
        if (c_error) {
            std::cerr << (*pPQuery)->ar_name << " - " << ::gai_strerror(c_error) << std::endl;
        }
        else {
            std::cout << (*pPQuery)->ar_name << " - " << *((*pPQuery)->ar_result) << std::endl;
            ::freeaddrinfo((*pPQuery)->ar_result);
        }
    }
    std::cout << number_of_threads() << " threads" << std::endl;
//    ::sigaction(my_sig_handler.sigev_signo, &old_sigaction, NULL);
}

std::ostream& operator<<(std::ostream &out, const struct ::addrinfo &ai)
{
    for (const struct ::addrinfo *pAi = &ai; pAi != NULL; pAi = pAi->ai_next) {
        if (pAi->ai_family == AF_INET) {
            out << reinterpret_cast< struct ::sockaddr_in* >(pAi->ai_addr)->sin_addr;
        }
        else if (pAi->ai_family == AF_INET6) {
            out << reinterpret_cast< struct ::sockaddr_in6* >(pAi->ai_addr)->sin6_addr;
        }
        if (pAi->ai_next != NULL) {
            out << " ";
        }
    }
    return out;
}

std::ostream& operator<<(std::ostream &out, const struct ::in_addr &a)
{
    char buf[INET_ADDRSTRLEN];
    out << ::inet_ntop(AF_INET, &a, buf, sizeof(buf));
    return out;
}

std::ostream& operator<<(std::ostream &out, const struct ::in6_addr &a)
{
    char buf[INET6_ADDRSTRLEN];
    out << ::inet_ntop(AF_INET6, &a, buf, sizeof(buf));
    return out;
}

//void getaddrinfo_sighandler(int sig_num, ::siginfo_t *sig_info, void*)
//{
////    const struct ::gaicb **const pQuery = const_cast< const struct ::gaicb** >(sig_info->si_value.sival_ptr);
//    *reinterpret_cast< bool* >(sig_info->si_value.sival_ptr) = true;
//}

::size_t number_of_threads()
{
    std::ostringstream proc_status;
    proc_status << "/proc/" << ::getpid() << "/status";
    const int fd = ::open(proc_status.str().c_str(), O_RDONLY);
    char status[0x10000];
    const ::size_t bytes = ::read(fd, status, sizeof(status) - 1);
    ::close(fd);
    status[bytes] = '\0';
    static const char to_find[] = "\nThreads:";
    const char *thr = std::strstr(status, to_find);
    if (thr == NULL) {
        return 0;
    }
    ::size_t n = 0;
    const char *pC = thr + sizeof(to_find) - 1;
    while ((*pC == ' ') || (*pC == '\t')) {
        ++pC;
    }
    while (('0' <= *pC) && (*pC <= '9')) {
        n = 10 * n + (*pC) - '0';
        ++pC;
    }
    return n;
}

int main(int argc, char *argv[])
{
//    std::cout << PACKAGE_STRING << std::endl;
    VecOfString host;
    if (argc <= 1) {
        host.push_back("www.yahoo.com");
        host.push_back("www.to-snad-ani-nemuze-existovat.cz");
        host.push_back("www.google.com");
        host.push_back("www.boost.org");
        host.push_back("www.ociweb.com");
        host.push_back("dev.mistrnet.cz");
        host.push_back("www.seznam.cz");
    }
    else {
        for (const char *const *pHost = argv + 1; *pHost != NULL; ++pHost) {
            host.push_back(*pHost);
        }
    }
    gnu_test(host);
    return EXIT_SUCCESS;
}
